﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Psm.PubSub.Messaging;
using System.IO;
using Psm.Util;

namespace Psm.PubSub.Serialization
{
    public class Constants
    {
        public static readonly char FIELD_SEP_CHAR = '\u0001';
        public static readonly char TUPLE_SEP_CHAR = '\u0002';
        public static readonly char VALUE_SEP_CHAR = '\u0003';

        public static readonly byte FIELD_SEP_BYTE = 0x01;
        public static readonly byte TUPLE_SEP_BYTE = 0x02;
        public static readonly byte VALUE_SEP_BYTE = 0x03;
    }

    public sealed class MessageSerializer
    {   
        private ByteBuffer _buffer;

        public MessageSerializer(PubSubMessageType messageType, ByteBuffer buffer)
        {
            _buffer = buffer;
            _buffer.Write((byte)messageType);
        }

        public bool AppendField(string item1)
        {
            if ( !_buffer.Write(Constants.FIELD_SEP_CHAR) ) return false;
            if ( !_buffer.Write(item1) ) return false;
            return true;
        }

        public bool AppendTuple(string item1, string item2)
        {
            if( !_buffer.Write(Constants.FIELD_SEP_CHAR) ) return false;
            if( !_buffer.Write(item1)) return false;
            if( !_buffer.Write(Constants.TUPLE_SEP_CHAR) ) return false;
            if (!_buffer.Write(item2)) return false;
            return true;
        }

        public bool AppendField(byte[] item1)
        {
            if (!_buffer.Write(Constants.FIELD_SEP_CHAR)) return false;
            if (!_buffer.Write(item1)) return false;
            return true;
        }

        public bool AppendTuple(byte[] item1, byte[] item2)
        {
            if (!_buffer.Write(Constants.FIELD_SEP_CHAR)) return false;
            if (!_buffer.Write(item1)) return false;
            if (!_buffer.Write(Constants.TUPLE_SEP_CHAR)) return false;
            if (!_buffer.Write(item2)) return false;
            return true;
        }

        public byte[] GetBytes()
        {
            return _buffer.GetBytes();
        }

        public bool AppendTuple(string item1, string item2, string item3)
        {
            if ( !_buffer.Write(Constants.FIELD_SEP_CHAR) ) return false;
            if ( !_buffer.Write(item1) ) return false;
            if ( !_buffer.Write(Constants.TUPLE_SEP_CHAR) ) return false;
            if ( !_buffer.Write(item2) ) return false;
            if ( !_buffer.Write(Constants.TUPLE_SEP_CHAR) ) return false;
            if ( !_buffer.Write(item3) ) return false;
            return true;
        }

        public bool AppendTuple(byte[] item1, byte[] item2, byte[] item3)
        {
            if (!_buffer.Write(Constants.FIELD_SEP_CHAR)) return false;
            if (!_buffer.Write(item1)) return false;
            if (!_buffer.Write(Constants.TUPLE_SEP_CHAR)) return false;
            if (!_buffer.Write(item2)) return false;
            if (!_buffer.Write(Constants.TUPLE_SEP_CHAR)) return false;
            if (!_buffer.Write(item3)) return false;
            return true;
        }

        public bool AppendFixField(string item)
        {
            return _buffer.WritePrefixed(item);
        }

        public bool AppendFixTuple(int item1, string item2)
        {
            if (!_buffer.Write(item1)) return false;
            return _buffer.WritePrefixed(item2);
        }

        public bool AppendFixTuple(string item1, string item2, string item3)
        {
            if (!AppendFixField(item1)) return false;
            if (!AppendFixField(item2)) return false;
            if (!AppendFixField(item3)) return false;
            return true;
        }
    }

    public sealed class MessageDeserializer
    {   
        private int _currentIndex;
        private ByteBuffer _buffer = null;

        public MessageDeserializer(ByteBuffer buffer, int padding = 2)
        {
            _buffer = buffer;
            _currentIndex = buffer.StartIndex + padding;
        }

        public PubSubMessageType GetMessageType()
        {
            return (PubSubMessageType)_buffer.GetBuffer()[0];
        }

        public bool HasMore()
        {
            return _currentIndex <= _buffer.EndIndex;
        }


        //marked format
        public string GetField()
        {
            return GetNextValue(Constants.FIELD_SEP_BYTE);
        }     

        public void GetTuple(out string item1, out string item2)
        {
            item1 = GetNextValue(Constants.TUPLE_SEP_BYTE);
            item2 = GetNextValue(Constants.FIELD_SEP_BYTE);
        }

        public void GetTuple(out string item1, out string item2, out string item3)
        {   
            item1 = GetNextValue(Constants.TUPLE_SEP_BYTE);
            item2 = GetNextValue(Constants.TUPLE_SEP_BYTE);
            item3 = GetNextValue(Constants.FIELD_SEP_BYTE);
        }


        private string GetNextValue(byte sepChar)
        {
            string value = null;
            int startPos = _currentIndex;
            byte[] data = _buffer.GetBuffer();
            while (_currentIndex <= _buffer.EndIndex && data[_currentIndex] != sepChar)
                _currentIndex++;
            if (startPos != _currentIndex)
                value = GetValue(data, startPos, _currentIndex - startPos);
            _currentIndex++;
            return value;
        }

        private string GetValue(byte[] data, int index, int count)
        {
            string value;
            value = ASCIIEncoding.ASCII.GetString(data, index, count);
            if (string.IsNullOrEmpty(value))
                return null;
            else
                return value;
        }



        //byte based
        public ByteBuffer GetByteField()
        {
            return GetNextByteValue(Constants.FIELD_SEP_BYTE);
        }

        public void GetByteTuple(out ByteBuffer item1, out ByteBuffer item2)
        {
            item1 = GetNextByteValue(Constants.TUPLE_SEP_BYTE);
            item2 = GetNextByteValue(Constants.FIELD_SEP_BYTE);
        }

        public void GetByteTuple(out  ByteBuffer item1, out ByteBuffer item2, out ByteBuffer item3)
        {
            item1 = GetNextByteValue(Constants.TUPLE_SEP_BYTE);
            item2 = GetNextByteValue(Constants.TUPLE_SEP_BYTE);
            item3 = GetNextByteValue(Constants.FIELD_SEP_BYTE);
        }

        private ByteBuffer GetNextByteValue(byte sepChar)
        {
            ByteBuffer value = null;
            int startPos = _currentIndex;
            byte[] data = _buffer.GetBuffer();
            while (_currentIndex <= _buffer.EndIndex && data[_currentIndex] != sepChar)
                _currentIndex++;
            if (startPos != _currentIndex)
                value = new ByteBuffer(data, startPos, _currentIndex, _currentIndex - startPos);
            _currentIndex++;
            return value;
        }



        //fixed
        public string GetFixStringField()
        {
            ByteBuffer buffer = GetFixField();
            return ASCIIEncoding.ASCII.GetString(buffer.GetBuffer(), buffer.StartIndex, buffer.Length);
        }

        public ByteBuffer GetFixField()
        {
            return GetFixBufferValue();
        }

        public void GetFixTuple(out int item1, out ByteBuffer item2)
        {
            item1 = GetFixIntValue();
            item2 = GetFixBufferValue();
        }

        public void GetFixTuple(out ByteBuffer item1, out ByteBuffer item2, out ByteBuffer item3)
        {
            item1 = GetFixBufferValue();
            item2 = GetFixBufferValue();
            item3 = GetFixBufferValue();
        }

        private ByteBuffer GetFixBufferValue()
        {
            int length = GetFixIntValue();
            ByteBuffer buffer = new ByteBuffer(_buffer.GetBuffer(), _currentIndex, _currentIndex + length, _currentIndex + length);
            _currentIndex += length;
            return buffer;

        }

        private int GetFixIntValue()
        {
            int value = BitConverter.ToInt32(_buffer.GetBuffer(), _currentIndex);
            _currentIndex += 4;
            return value;
        }
       
    }
}
